package top.rainbowecho.gateway.security.captcha;

import com.dingxianginc.ctu.client.CaptchaClient;
import com.dingxianginc.ctu.client.model.CaptchaResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.filter.OncePerRequestFilter;
import top.rainbowecho.common.util.CommonUtil;
import top.rainbowecho.common.util.ExceptionMessageContent;
import top.rainbowecho.common.util.RequestKey;
import top.rainbowecho.gateway.security.config.GatewayApiConstants;
import top.rainbowecho.gateway.security.property.SecurityProperties;
import top.rainbowecho.gateway.security.support.JsonAuthenticationFailureHandler;

import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import static cn.hutool.core.collection.CollUtil.addAll;
import static org.springframework.web.bind.ServletRequestUtils.getStringParameter;

/**
 * @author rainbow
 * @since 2019/12/14 19:28
 */
@Component
@Slf4j
public class CaptchaOnceFilter extends OncePerRequestFilter {
    private JsonAuthenticationFailureHandler authenticationFailureHandler;

    private SecurityProperties securityProperties;

    private Set<String> urls = new HashSet<>(4);

    private AntPathMatcher pathMatcher;

    @Autowired
    public void setPathMatcher(AntPathMatcher pathMatcher) {
        this.pathMatcher = pathMatcher;
    }

    @PostConstruct
    public void setup() {
        String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCaptcha().getUrls(), ",");
        if (configUrls != null && configUrls.length > 0) {
            addAll(this.urls, configUrls);
        }
        this.urls.add(GatewayApiConstants.FORM_PROCESS_URL);
    }

    @Autowired
    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    @Autowired
    public void setAuthenticationFailureHandler(JsonAuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        boolean isCaptchaFilterPath = CommonUtil.pathMatch(request, this.urls, pathMatcher);
        if (isCaptchaFilterPath && HttpMethod.POST.matches(request.getMethod())) {
            try {
                log.info("准备开始进行人机验证");
                captcha(request);
            } catch (CaptchaAuthenticationException e) {
                // 失败处理器处理
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }

        filterChain.doFilter(request, response);
    }

    private void captcha(HttpServletRequest request) throws ServletRequestBindingException {
        String captchaToken = getStringParameter(request, RequestKey.CAPTCHA);

        if (StringUtils.isEmpty(captchaToken)) {
            throw new CaptchaAuthenticationException(ExceptionMessageContent.EMPTY_CAPTCHA_TOKEN);
        }

        String appId = securityProperties.getCaptcha().getApp();
        String secretKey = securityProperties.getCaptcha().getSecret();

        CaptchaClient captchaClient = new CaptchaClient(appId, secretKey);
        try {
            CaptchaResponse captchaResponse = captchaClient.verifyToken(captchaToken);
            log.info("captcha验证状态: " + captchaResponse.getCaptchaStatus());
            if (!captchaResponse.getResult()) {
                throw new CaptchaAuthenticationException(ExceptionMessageContent.CAPTCHA_FAIL);
            }
        } catch (Exception e) {
            throw new CaptchaAuthenticationException(e.getMessage());
        }
    }
}
